#include "stdafx.h"
#include "win_cmd.h"
#include "win_app.h"
#include "h2image.h"

void cmd_history::add(const wchar_t* pwscmd)  
{
    if(!pwscmd || (_history.size() > 0 && 0 == wcscmp(*_history.back(), pwscmd)))
    {
        reset_cursor();
        return;
    }

    _history.push_back(STRING::unicode(pwscmd)); 

    if(_history.size() > 100)
    {
        _history.erase(_history.begin(), _history.begin() + (_history.size() - 100));
    }

    reset_cursor();
}



STRING::unicode cmd_history::search_forward(const wchar_t* keyword)
{
    int len = keyword ? wcslen(keyword) : 0;

    for(_cursor = max(0, _cursor + 1); _cursor < (int)_history.size(); ++_cursor)
    {
        if(len > _history[_cursor].length())
        {
            continue;
        }

        if(!keyword || 0 == _wcsnicmp(keyword, *_history[_cursor], len))
        {
            return *_history[_cursor]+len;
        }
    }

    return L"";
}


STRING::unicode cmd_history::search_backward(const wchar_t* keyword)
{
    int len = keyword ? wcslen(keyword) : 0;

    for(_cursor = min((int)_history.size() - 1, _cursor - 1); _cursor >= 0; --_cursor)
    {
        if(len > _history[_cursor].length())
        {
            continue;
        }

        if(!keyword || 0 == _wcsnicmp(keyword, *_history[_cursor], len))
        {
            return *_history[_cursor]+len;
        }
    }

    return L"";
}



win_cmd::win_cmd()
{
    _dwExStyle = WS_EX_PALETTEWINDOW | WS_EX_CLIENTEDGE;
    _dwStyle = WS_CHILD | WS_VSCROLL | WS_CLIPCHILDREN;
    _caret_pos = 0;
    _caret_flag = 0;
    _select_from = 0;
    _max_lines = 500;
    _cmd_type = 0;
}


win_cmd::~win_cmd()
{
}


bool win_cmd::create(HWND hWndParent)
{
    bool res = win_base::create(hWndParent);

    if(res)
    {
        init_canvas(_hWnd);
        set_margin(4, 4, 4, 4);
        clear_view();
        SetTimer(_hWnd, TIMER_CARET, 500, NULL);
    }

    OnInit();
    return res;
}


LRESULT win_cmd::OnPaint()
{
    RECT rc;
    GetClientRect(_hWnd, &rc);

    PAINTSTRUCT ps;
    HDC hdc = BeginPaint(_hWnd, &ps);
    output_canvas(hdc, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top);
    Gdiplus::Graphics graphics(hdc);
    if(graphics.GetLastStatus() == Gdiplus::Ok)
    {
        //graphics.DrawImage(H2IMG::bitmap("js"), 0, 0);
    }
    EndPaint(_hWnd, &ps);
    return 0;
}

LRESULT win_cmd::OnLeftDown(WORD x, WORD y, BOOL bCtrlDown, BOOL bShiftDown)
{
    SetFocus(_hWnd);

    int pos = pos_from_point(x,y) - input_head();
    if(pos >= 0)
    {
        move_input(pos);
    }

    if(GetCapture() == NULL)
    {
        if(!(GetKeyState(VK_SHIFT)&0x8000) && !(GetKeyState(VK_CONTROL)&0x8000))
        {
            clear_selection();
        }

        _drag_pt.x = view_to_canvas_x(x);
        _drag_pt.y = view_to_canvas_y(y);
        SetCapture(_hWnd);
    }

    InvalidateRect(_hWnd, NULL, TRUE);
    return 0;
}

LRESULT win_cmd::OnLeftUp(WORD x, WORD y, BOOL bCtrlDown, BOOL bShiftDown)
{
    ReleaseCapture();
   
    int idx = mark_count() - 1;
    if(idx >= 0)
    {
        if(mark_at(idx).end > input_head())
        {
            move_input(max(0, mark_at(idx).begin - input_head()));
            move_input(mark_at(idx).end - input_head(), true);
        }
        else
        {
            select_mark();
        }
        clear_mark();
    }

    redraw_canvas(true);
    InvalidateRect(_hWnd, NULL, TRUE);
    return 0;
}

LRESULT win_cmd::OnLeftDoubleClick(WORD x, WORD y, BOOL bCtrlDown, BOOL bShiftDown)
{
    int pos = pos_from_point(x, y);
    text_range range;
    if(pos >= 0 && word_range_from_pos(pos, &range))
    {
        clear_selection();
        select_text(range);
        InvalidateRect(_hWnd, NULL, TRUE);
    }

    return 0;    
}


LRESULT win_cmd::OnRightDown(WORD x, WORD y, BOOL bCtrlDown, BOOL bShiftDown)
{
    STRING::unicode text = get_sel_text();
    if(text.length() > 0)
    {
        copy_to_clipboard(*text);
        paste_text(*text);
    }
    else
    {
        paste_text(*get_clipboard_text());
    }

    return 0;
}

LRESULT win_cmd::OnMouseMove(DWORD dwKeys, WORD x, WORD y)
{
    if(GetCapture() != _hWnd)
    {
        return 0;
    }

    POINT ptCur;
    GetCursorPos(&ptCur);
    ScreenToClient(_hWnd, &ptCur);
    ptCur.x = view_to_canvas_x(ptCur.x);
    ptCur.y = view_to_canvas_y(ptCur.y);

    RECT rcMark = { min(ptCur.x, _drag_pt.x), min(ptCur.y, _drag_pt.y),
                    max(ptCur.x, _drag_pt.x), max(ptCur.y, _drag_pt.y) };

    bool full_line = false, single_range = false;

    int count = mark_text(rcMark, (GetKeyState(VK_CONTROL)&0x8000) == 0, (GetKeyState(VK_SHIFT)&0x8000) != 0);

    redraw_canvas(false);
    InvalidateRect(_hWnd, NULL, TRUE);
    return 0;
}

LRESULT win_cmd::OnMouseWheel(WORD x, WORD y, WORD key, short distance)
{
    int iScrollCode = SB_PAGEUP;
    if(distance < 0)
    {
        iScrollCode = SB_PAGEDOWN;
    }

    OnVerScroll(iScrollCode, 0, NULL);

    return 0;
}


LRESULT win_cmd::OnSize(DWORD dwFlags, WORD width, WORD height)
{
    set_max_canvas_width(max(width, 200));
    resize_canvas(width, height);
    update_view();
    redraw_canvas();
    return 0;
}


LRESULT win_cmd::OnHorScroll(int scrollCode, int pos, HWND scrollBar)
{
    int iPos = GetScrollPos(_hWnd, SB_HORZ);

    switch(scrollCode)
    {
    case SB_LINELEFT:
        iPos --;
        break;

    case SB_LINERIGHT:
        iPos ++;
        break;

    case SB_PAGELEFT:
        iPos -= 3;
        break;

    case SB_PAGERIGHT:
        iPos += 3;
        break;

    case SB_THUMBTRACK:
        iPos = pos;
    default:
    break;
    }

    iPos = min(max(iPos, 0), get_max_h_scroll());
    if(iPos != get_h_scroll())
    {
        SetScrollPos(_hWnd, SB_HORZ, iPos, TRUE);
        scroll_view(iPos, get_v_scroll());
        InvalidateRect(_hWnd, NULL, TRUE);
    }

    return 0;
}

LRESULT win_cmd::OnVerScroll(int scrollCode, int pos, HWND scrollBar)
{
    int iPos = GetScrollPos(_hWnd, SB_VERT);
    switch(scrollCode)
    {
    case SB_LINEUP:
        iPos --;
        break;

    case SB_LINEDOWN:
        iPos ++;
        break;

    case SB_PAGEUP:
        iPos -= 3;
        break;

    case SB_PAGEDOWN:
        iPos += 3;
        break;

    case SB_THUMBTRACK:
        iPos = pos;
    default:
    break;
    }

    iPos = min(max(iPos, 0), get_max_v_scroll());
    if(iPos != get_v_scroll())
    {
        SetScrollPos(_hWnd, SB_VERT, iPos, TRUE);
        scroll_view(get_h_scroll(), iPos);
        InvalidateRect(_hWnd, NULL, TRUE);
    }


    return 0;
}

void win_cmd::clear_view()
{
    clear_text();
    insert_text(0, *_prompt);
    insert_text(0, "\n");
    move_input(0);
}


void win_cmd::output(const wchar_t* pwszText)
{
    if(NULL == pwszText)
    {
        // internal error
        return;
    }

    insert_text(_line_buffer.back().range.begin - 1, pwszText);
    update_view();
}

void win_cmd::update_view()
{
    // make sure lines less than 100
    int scroll_h_pos = get_h_scroll();
    int scroll_v_pos = get_v_scroll();
    if(line_count() > _max_lines)
    {
        // remove 1/10 lines everytime avoid screen scrolling frequently
        int remove_count = max(1, _max_lines / 10);
        while(line_count() > 0 && line_count() > _max_lines - remove_count)
        {
            remove_text(_line_buffer[0].range);
            scroll_v_pos --;
        }
    }

    int iMaxVScroll = get_max_v_scroll();
    int iMaxHScroll = get_max_h_scroll();
    int iMinPos, iMaxPos;
    GetScrollRange(_hWnd, SB_VERT, &iMinPos, &iMaxPos);
    if(iMaxPos != iMaxVScroll)
    {
        SetScrollRange(_hWnd, SB_VERT, 0, iMaxVScroll, TRUE);
    }
    GetScrollRange(_hWnd, SB_HORZ, &iMinPos, &iMaxPos);
    if(iMaxPos != iMaxHScroll)
    {
        SetScrollRange(_hWnd, SB_HORZ, 0, iMaxHScroll, TRUE);
    }

    if(scroll_v_pos < 0)
    {
        scroll_view(0,0);
    }
    else
    {
        scroll_view(scroll_h_pos, scroll_v_pos);
    }


    SetScrollPos(_hWnd, SB_HORZ, scroll_h_pos, TRUE);
    SetScrollPos(_hWnd, SB_VERT, scroll_v_pos, TRUE);

    InvalidateRect(_hWnd, NULL, TRUE);
}


LRESULT win_cmd::OnChar(DWORD dwKey, DWORD dwData)
{

    switch(dwKey)
    {
    case 0x03:          // CTRL + C
        {
            STRING::unicode text = get_sel_text();
            if(text.length() > 0)
            {
                copy_to_clipboard(*text);
            }
        }return 0;

    case 0x18:          // CTRL + X
        {
            STRING::unicode text = get_sel_text();
            if(text.length() > 0)
            {
                copy_to_clipboard(*text);
            }
            if(selection_count() > 0 && selection_at(0).begin >= input_head())
            {
                remove_sel_text();
            }
        }break;

    case 0x16:          // CTRL + V
        paste_text(*get_clipboard_text());
        break;

    default:
        if(dwKey < 0x20)
        {
            return 0;
        }
    case 0x09:          // TAB

        // remove selected text first
        if(selection_count() > 0 && selection_at(0).end > input_head())
        {
            remove_sel_text();
        }
        wchar_t tmp[2] = {(wchar_t)dwKey, 0};
        insert_text(caret_pos(), tmp);
        break;
    }

    _cmd_history.reset_cursor();
    update_view();
    move_input(input_pos());
    scroll_view_bottom();
    return 0;
}


LRESULT win_cmd::OnKeyDown(DWORD dwKey, DWORD dwKeyData)
{
    switch(dwKey)
    {
    case VK_ESCAPE:
        input_clear();
        break;

    case VK_TAB:
        if((GetKeyState(VK_CONTROL)&0x8000))
        {
            OnSwitch();
        }
        break;

    case VK_LEFT:
        {
            move_input(input_pos() - 1, (GetKeyState(VK_SHIFT)&0x8000) != 0);
        }break;

    case VK_RIGHT:
        {
            move_input(input_pos() + 1, (GetKeyState(VK_SHIFT)&0x8000) != 0);
        }break;

    case VK_UP:
        {
            if((GetKeyState(VK_SHIFT)&0x8000))
            {
                move_input(0, true);
            }
            else
            {
                if(selection_count() > 0 && selection_at(0).begin >= input_head())
                {
                    remove_sel_text();
                }
                move_input(input_length());
                STRING::unicode txt = input_text();
                insert_text(TEXT_END, *_cmd_history.search_backward(*txt));
                move_input(input_length(), true);
            }
        }break;

    case VK_DOWN:
        {
            if((GetKeyState(VK_SHIFT)&0x8000))
            {
                move_input(input_length(), true);
            }
            else
            {
                if(selection_count() > 0 && selection_at(0).begin >= input_head())
                {
                    remove_sel_text();
                }
                move_input(input_length());
                STRING::unicode txt = input_text();
                insert_text(TEXT_END, *_cmd_history.search_forward(*txt));
                move_input(input_length(), true);
            }
        }break;
        
    case VK_RETURN:
        move_input(input_length());
        paste_text(L"\n");
        break;
        
    case VK_DELETE:
        if(selection_count() > 0 && selection_at(selection_count()-1).begin >= input_head())
        {
            remove_text(selection_at(selection_count()-1));
        }
        else if(input_length() > 0)
        {
            text_range range = {caret_pos(), caret_pos()+1};
            remove_text(range);
        }
        clear_selection();
        move_input(input_pos());
        break;
        
    case VK_BACK:
        if(selection_count() > 0 && selection_at(selection_count()-1).begin >= input_head())
        {
            remove_text(selection_at(selection_count()-1));
        }
        else if(input_pos() > 0)
        {
            text_range range = {caret_pos()-1, caret_pos()};
            remove_text(range);
        }
        clear_selection();
        move_input(input_pos());
        break;
    default:
        return 0;
    }

    scroll_view_bottom();
    InvalidateRect(_hWnd, NULL, TRUE);
    return 0;
}


LRESULT win_cmd::OnTimer(DWORD dwTimerID, TIMERPROC* pTimeProc)
{
    if(dwTimerID != TIMER_CARET)
    {
        return win_base::OnTimer(dwTimerID, pTimeProc);
    }

    _caret_flag = abs(_caret_flag - 1);
    show_caret(_caret_flag != 0);

    InvalidateRect(_hWnd, NULL, TRUE);

    return 0;
}


void win_cmd::paste_text(const wchar_t* pwtext)
{
    if(selection_count() > 0 && selection_at(0).begin > input_head())
    {
        remove_sel_text();
    }
    clear_selection();

    STRING::unicode cmds(pwtext);
    while(cmds.length() > 0)
    {
         // paste text to current caret
        STRING::unicode txt = STRING::split(cmds, L"\n");
        insert_text(caret_pos(), *txt);

        if(cmds.is_null())
        {
            break;
        }

        // get command line
        text_range cmd_range = { input_head(), input_head() + input_length() };
        STRING::unicode cmd = get_text(cmd_range);
        insert_text(TEXT_END, L"\n\n");
        insert_text(TEXT_END, *_prompt);
        move_caret(TEXT_END);
        move_input(0);
        if(cmd.size() > 0)
        {
            OnCommand(*cmd);
            _cmd_history.add(*cmd);
        }
    }

    update_view();
    scroll_view_bottom();
}

void win_cmd::scroll_view_bottom()
{
    int iPos = max(get_max_v_scroll(), 0);
    if(iPos != get_v_scroll())
    {
        SetScrollPos(_hWnd, SB_HORZ, get_h_scroll(), TRUE);
        SetScrollPos(_hWnd, SB_VERT, iPos, TRUE);
        scroll_view(get_h_scroll(), iPos);
    }
}


void win_cmd::move_input(int pos, bool marked)
{
    int new_pos = input_head() + max(0, pos); new_pos = min(input_tail(), new_pos);

    move_caret(new_pos); 

    if(!marked)
    {
        _select_from = pos;
        clear_selection();
    }
    else
    {
        text_range sel = { min(_select_from + input_head(), new_pos), max(_select_from + input_head(), new_pos) };
        select_text(sel);
    }

    _caret_flag = 2; 
    show_caret(); 
}


void win_cmd::copy_to_clipboard(wchar_t* pwtext)
{
    int len = 0;
    if(!pwtext || (len = wcslen(pwtext)) <= 0)
    {
        return;
    }

    HGLOBAL hClip;
    if (OpenClipboard(_hWnd))
    {
        EmptyClipboard();
        hClip = GlobalAlloc(GMEM_MOVEABLE, (len + 1) * sizeof(wchar_t));
        wchar_t* pwclip = (wchar_t*)GlobalLock(hClip);
        wcscpy_s(pwclip, len + 1, pwtext);
        GlobalUnlock(hClip);
        SetClipboardData(CF_UNICODETEXT, hClip);
        CloseClipboard();
    }
}


STRING::unicode win_cmd::get_clipboard_text()
{
    STRING::unicode cliptext;

    if (OpenClipboard(_hWnd))
    {
        if (IsClipboardFormatAvailable(CF_UNICODETEXT))
        {
            HANDLE hClip=::GetClipboardData(CF_UNICODETEXT);
            cliptext = STRING::unicode((wchar_t*) GlobalLock(hClip));
            GlobalUnlock(hClip);
        }
        else if(IsClipboardFormatAvailable(CF_TEXT))
        {
            HANDLE hClip=::GetClipboardData(CF_TEXT);
            char* pclip = (char*) GlobalLock(hClip);
            cliptext = STRING::unicode(pclip);
            GlobalUnlock(hClip);
        }
        CloseClipboard();
    }

    return cliptext;
}